﻿using MX.Core.Caching.Redis.Sider;
using System.Linq;

namespace MX.Core.Caching.Redis
{
	using MX.Core.Caching;

	using MX.Core.Caching.Redis.Configuration;

	using Newtonsoft.Json;

	using System;
	using System.Collections.Generic;
	using System.Configuration;
	using System.Runtime.Caching;
	using System.Runtime.InteropServices;
	using System.Text;

	public class RedisCaching : IDisposable, ICaching
	{

		private string _sectionName;
		private bool _isDisposed;
		private Dictionary<string, HostFileChangeMonitor> _monitors;


		public RedisCaching () : this (null)
		{
		}

		public RedisCaching (string sectionName)
		{
			this._sectionName = sectionName;

			this._monitors = new Dictionary<string, HostFileChangeMonitor> (100, StringComparer.InvariantCultureIgnoreCase);
			Init ();
		}


		/// <summary>
		/// 初始化线程池
		/// </summary>
		private void Init ()
		{
			this._monitors = new Dictionary<string, HostFileChangeMonitor> (100, StringComparer.InvariantCultureIgnoreCase);

			ConfigSetting config = GetConfig ();
			SockIOPool pool = SockIOPool.GetInstance ();
			pool.SetServers (config.Servers);
			pool.InitConnections = config.InitConnections;
			pool.MinConnections = config.MinConnections;
			pool.MaxConnections = config.MaxConnections;
			pool.SocketConnectTimeout = config.SocketConnectTimeout;
			pool.SocketTimeout = config.SocketTimeout;
			pool.MaintenanceSleep = config.MaintenanceSleep;
			pool.Failover = true;
			pool.Nagle = false;
			pool.Initialize ();
		}

		/// <summary>
		/// 获取配置
		/// </summary>
		/// <returns></returns>
		private ConfigSetting GetConfig ()
		{
			_sectionName = _sectionName ?? @"MX.Caching.Redis";

			var section = (RedisClientSection)System.Configuration.ConfigurationManager.GetSection (_sectionName);
			if (section != null) {
				var result = new ConfigSetting ();
				result.InitConnections = section.SocketPool.MinPoolSize;
				result.MaintenanceSleep = 30;
				result.MaxConnections = section.SocketPool.MaxPoolSize;
				result.MinConnections = section.SocketPool.MinPoolSize;
				result.SocketConnectTimeout = (int)section.SocketPool.ConnectionTimeout.TotalMilliseconds;
				result.SocketTimeout = (int)section.SocketPool.DeadTimeout.TotalMilliseconds;
				result.Servers = new string [section.Servers.Count];
				if (result.Servers.Length < 1) {
					throw new NotSupportedException ("memcached server undefine");
				} else {
					var sourceServers = section.Servers.ToIPEndPointCollection ();
					for (int i = 0; i < result.Servers.Length; i++) {
						var server = sourceServers [i];
						result.Servers [i] = server.Address + ":" + server.Port;
					}
				}
				return result;
			} else {
				throw new NotSupportedException ("memcached config happend some error");
			}
		}

		SiderRedisClient _client = new SiderRedisClient ();

		/// <summary>
		/// 获取客户端连接
		/// </summary>
		/// <returns></returns>
		public SiderRedisClient GetClient ()
		{
			return _client;
		}




		public long Decrement (string key, uint amount)
		{
			return GetClient ().DecrBy (key, amount);
		}

		public void Dispose ()
		{
			if (!this._isDisposed) {
				foreach (KeyValuePair<string, HostFileChangeMonitor> pair in this._monitors) {
					pair.Value.Dispose ();
				}
				this._monitors.Clear ();
				if (this._client != null) {
					this._client = null;
				}
				this._isDisposed = true;
			}
		}

		public bool Exists (string key)
		{

			return GetClient ().Exists (key);
		}



		public object Get (string key)
		{
			return BinaryFormatter.Deserialize<object> (GetClient ().Get (key));
		}

		public T Get<T> (string key)
		{
			return BinaryFormatter.Deserialize<T> (GetClient ().Get (key));
		}

		public IDictionary<string, object> GetAll (string [] keys)
		{
			var bufferArray = GetClient ().MGet (keys);
			Dictionary<string, object> dictionary = new Dictionary<string, object> ();
			for (int i = 0; i < keys.Length; i++) {
				dictionary [keys [i]] = BinaryFormatter.Deserialize<object> (bufferArray [i]);
			}
			return dictionary;

		}

		public IDictionary<string, T> GetAll<T> (string [] keys)
		{
			var bufferArray = GetClient ().MGet (keys);
			Dictionary<string, T> dictionary = new Dictionary<string, T> ();
			for (int i = 0; i < keys.Length; i++) {
				dictionary [keys [i]] = BinaryFormatter.Deserialize<T> (bufferArray [i]);
			}
			return dictionary;
		}




		public T GetOrAdd<T> (string key, Func<T> handler)
		{
			T local;
			if (!this.TryGet<T> (key, out local)) {
				local = handler ();
				if (Common.CheckValueNull (local)) {
					this.Set<T> (key, local);
				}
			}
			return local;
		}

		public T GetOrAdd<T> (string key, Func<T> handler, DateTimeOffset absoluteExpiration)
		{
			T local;
			if (!this.TryGet<T> (key, out local)) {
				local = handler ();
				if (Common.CheckValueNull (local)) {
					this.Set<T> (key, local, absoluteExpiration);
				}
			}
			return local;
		}

		public T GetOrAdd<T> (string key, Func<T> handler, params string [] files)
		{
			T local;
			if (!this.RegisterMoniter (key, files)) {
				return default (T);
			}
			if (!this.TryGet<T> (key, out local)) {
				local = handler ();
				if (Common.CheckValueNull (local)) {
					this.Set<T> (key, local);
				}
			}
			return local;
		}

		public T GetOrAdd<T> (string key, Func<T> handler, DateTimeOffset absoluteExpiration, params string [] files)
		{
			T local;
			if (!this.RegisterMoniter (key, files)) {
				return default (T);
			}
			if (!this.TryGet<T> (key, out local)) {
				local = handler ();
				if (Common.CheckValueNull (local)) {
					this.Set<T> (key, local, absoluteExpiration);
				}
			}
			return local;
		}

		public long Increment (string key, uint amount)
		{
			return GetClient ().IncrBy (key, amount);
		}


		private bool RegisterMoniter (string key, params string [] dependentFiles)
		{
			if (this._isDisposed) {
				return false;
			}
			string moniterKey = key + "|" + string.Join ("|", dependentFiles);
			if (!this._monitors.ContainsKey (moniterKey)) {
				lock (this._monitors) {
					if (!this._monitors.ContainsKey (moniterKey)) {
						HostFileChangeMonitor moniter = new HostFileChangeMonitor (dependentFiles);
						moniter.NotifyOnChanged (delegate (object state) {
							try {
								this.Remove (key);
								moniter.Dispose ();
							} catch {
							} finally {
								this._monitors.Remove (moniterKey);
								this.RegisterMoniter (key, dependentFiles);
							}
						});
						this._monitors.Add (moniterKey, moniter);
					}
				}

			}
			return true;
		}

		public bool Remove (string key)
		{

			return GetClient ().Del (key) > 0;
		}

		public void RemoveAll (IEnumerable<string> keys)
		{

			GetClient ().Del (keys.ToArray ());
		}

		public void Set (string key, object data)
		{

			GetClient ().Set (key, BinaryFormatter.Serialize (data));
		}

		public void Set<T> (string key, T data)
		{

			GetClient ().Set (key, BinaryFormatter.Serialize (data));
		}

		public void Set (string key, object data, DateTimeOffset absoluteExpiration)
		{

			GetClient ().SetEX (key, absoluteExpiration - DateTime.Now, BinaryFormatter.Serialize (data));
		}

		public void Set (string key, object data, params string [] dependentFiles)
		{
			if (this.RegisterMoniter (key, dependentFiles)) {
				this.Set (key, data);
			}
		}

		public void Set<T> (string key, T data, DateTimeOffset absoluteExpiration)
		{

			GetClient ().SetEX (key, absoluteExpiration - DateTime.Now, BinaryFormatter.Serialize (data));
		}

		public void Set<T> (string key, T data, params string [] dependentFiles)
		{
			if (this.RegisterMoniter (key, dependentFiles)) {
				this.Set<T> (key, data);
			}
		}

		public void Set (string key, object data, DateTimeOffset absoluteExpiration, params string [] dependentFiles)
		{
			if (this.RegisterMoniter (key, dependentFiles)) {
				this.Set (key, data, absoluteExpiration);
			}
		}

		public void Set<T> (string key, T data, DateTimeOffset absoluteExpiration, params string [] dependentFiles)
		{
			if (this.RegisterMoniter (key, dependentFiles)) {
				this.Set<T> (key, data, absoluteExpiration);
			}
		}

		public bool TryGet<T> (string key, out T value)
		{
			try {
				if (this.Exists (key)) {
					value = this.Get<T> (key);
					return true;
				}
			} catch {
			}
			value = default (T);
			return false;
		}


		public bool HSet<T> (string key, string field, T value)
		{
			return GetClient ().HSet (key, field, BinaryFormatter.Serialize (value));
		}

		public bool HDel (string key, string field)
		{
			return GetClient ().HDel (key, field);
		}


		public List<T> HVals<T> (string key)
		{
			List<T> list = new List<T> ();
			var datas = GetClient ().HVals (key);
			foreach (var data in datas) {
				list.Add (BinaryFormatter.Deserialize<T> (data));
			}
			return list;
		}

		private static ICaching _cacheInstance = null;
		static object _initLock = new object ();

		public static ICaching Default {
			get {
				if (_cacheInstance == null) {
					lock (_initLock) {
						if (_cacheInstance == null) {
							string sectionName = ConfigurationManager.AppSettings ["MX.Cache.Redis.SectionName"];
							_cacheInstance = new RedisCaching (sectionName);
						}
					}
				}
				return _cacheInstance;
			}
		}

	}
}


